#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <signal.h>
#include <time.h>

#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <net/if.h>

#include "list.h"

#define ADDRESS   "0.0.0.0"
#define PORT      11701
#define BUFLEN    512
#define LIFETIME  300

struct host {
  struct list_head lh;
  char id[255];
  char address[255];
  struct timeval time;
};

fd_set master_fd_set;
LIST_HEAD(hosts);


void close_fd (int sig) {
  int cskfd;
  for (cskfd = 0; cskfd < FD_SETSIZE; ++cskfd)
    if (FD_ISSET (cskfd, &master_fd_set))
      close (cskfd);

  exit (0);
}

void clean (int sig) {
  struct host* entry, *n;
  struct timeval time;
  gettimeofday (&time, NULL);
  list_for_each_entry_safe(entry, n, &hosts, lh) {
    if (entry->time.tv_sec < time.tv_sec - LIFETIME) {
      list_del (&entry->lh);
      free (entry);
    }
  }
}

int main (int argc, char** argv) {
  struct sockaddr_in addr, remote_addr;
  char buffer[BUFLEN];
  char* c;
  size_t addr_size;
  int i, skfd, cskfd;
  fd_set read_fd_set;

  FD_ZERO(&master_fd_set);
  FD_ZERO(&read_fd_set);

  signal (SIGINT, close_fd);
  signal (SIGUSR1, clean);

  addr.sin_family = AF_INET;
  addr.sin_port = htons (PORT);
  inet_pton (AF_INET, ADDRESS, &addr.sin_addr);
  memset (&addr.sin_zero, '\0', 8);
  skfd = socket (PF_INET, SOCK_STREAM, 0);

  if ( skfd == -1 ) {
    fprintf (stderr, "Could not open skfd\n" );
    return -1;
  }

  if (bind (skfd, (struct sockaddr *) &addr, sizeof (struct sockaddr)) == -1 ) {
    fprintf (stderr, "Error when binding socket\n");
    return -2;
  }

  if (listen (skfd, 5) == -1) {
    fprintf (stderr, "Error : listen\n");
    return -3;
  }

  FD_SET(skfd, &master_fd_set);

  while (1) {
    read_fd_set = master_fd_set;
    select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL);

    for (cskfd = 0; cskfd < FD_SETSIZE; cskfd++) {
      if (FD_ISSET (cskfd, &read_fd_set)) {
        
        if (cskfd == skfd) {
          /* new connexion */
          int new_skfd = accept (skfd, (struct sockaddr *) &remote_addr,
              &addr_size);

          if (new_skfd != -1) {
            FD_SET(new_skfd, &master_fd_set);
          }
        } else {
          /* already connected socket */
          bzero (buffer, BUFLEN);
          int o;
          if (o = read (cskfd, buffer, BUFLEN) > 0) {

            /* REGISTER */
            if (strncmp (buffer, "REGISTER;", 9) == 0) {
              struct host* new = malloc (sizeof (struct host));
              char* c = buffer+9;
              int id_len, address_len, found = 0;
              struct host* entry;
        
              for (id_len=0; (*c != ';' && id_len<255); id_len++,c++);
              list_for_each_entry(entry, &hosts, lh) {
                if (strncmp (entry->id, buffer+9, id_len) == 0 &&
                    entry->id[id_len] == '\0') {
                  found = 1;
                  break;
                }
              }

              if (found) {
                gettimeofday (&(entry->time), NULL);
              } else {
                strncpy (new->id, buffer+9, id_len);
                new->id[id_len] = '\0';

                c++;

                for (address_len=0; (*c != ';' && address_len<255);
                    address_len++,c++);
                strncpy (new->address, buffer+id_len+9+1, address_len);
                new->address[address_len] = '\0';

                gettimeofday (&(new->time), NULL);
                list_add (&new->lh, &hosts);
              }
            }

            /* GET */
            if (strncmp (buffer, "GET;", 4) == 0) {
              int n = 0;
              char resp[1024]= {0};
              struct host* entry;

              kill (getpid (), SIGUSR1);

              list_for_each_entry(entry, &hosts, lh)
                n++;
              
              sprintf (resp, "LIST;%d;", n);
              send (cskfd, resp, sizeof (resp), 0);

              list_for_each_entry(entry, &hosts, lh) {
                bzero (resp, BUFLEN);
                snprintf (resp, BUFLEN, "LISTELMT;%s;%s;", entry->id,
                    entry->address);
                send (cskfd, resp, sizeof (resp), 0);
              }
            }

          } else {
            FD_CLR(cskfd, &read_fd_set);
            close (cskfd);
          }
        }
      }
    }

  }

  return 0;
}

